package main

import (
	"database/sql"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"strings"

	_ "modernc.org/sqlite"
)

// IconJSON 结构体用于解析 icon_json 字段
type IconJSON struct {
	ItemType        int    `json:"itemType"`
	Src             string `json:"src"`
	Text            string `json:"text"`
	BackgroundColor string `json:"backgroundColor"`
}

// replaceIPExactly 在 s 中查找 fromIP，仅当它是一个“完整IPv4”的独立实例时才替换
// 即：后面不能紧跟数字（防止是10.98.8.1134），但可以跟 .数字（如 .443）、字母、符号等
func replaceIPExactly(s, fromIP, toIP string) (string, bool) {
	if fromIP == "" {
		return s, false
	}

	var result strings.Builder
	start := 0
	found := false

	for {
		idx := strings.Index(s[start:], fromIP)
		if idx == -1 {
			break
		}
		realIdx := start + idx
		endIdx := realIdx + len(fromIP)

		// 检查前面字符：不能是数字或点（防止是更大IP的一部分，如 x10.98.8.113 或 192.168.10.98.8.113）
		prevOK := true
		if realIdx > 0 {
			prevChar := s[realIdx-1]
			if (prevChar >= '0' && prevChar <= '9') || prevChar == '.' {
				prevOK = false
			}
		}

		// 检查后面字符：
		nextOK := true
		if endIdx < len(s) {
			nextChar := s[endIdx]
			// 如果后面是数字 → 说明是 10.98.8.1134 → 不替换
			if nextChar >= '0' && nextChar <= '9' {
				nextOK = false
			}
			// 如果后面是点，再检查点后是否是数字 → 如果是，说明是 10.98.8.113.443 → ✅ 可以替换
			// 因为 .443 是另一个段，不属于原IP
			// 所以不需要阻止 —— 默认允许
		}

		if prevOK && nextOK {
			// 执行替换
			result.WriteString(s[start:realIdx])
			result.WriteString(toIP)
			start = endIdx
			found = true
		} else {
			// 不替换，跳过这个匹配
			result.WriteString(s[start : realIdx+1]) // 只前进1位，避免死循环
			start = realIdx + 1
		}
	}

	if !found {
		return s, false
	}

	result.WriteString(s[start:])
	return result.String(), true
}

func main() {
	var dbPath, fromIP, toIP string

	flag.StringVar(&dbPath, "db", "", "SQLite database file path (required)")
	flag.StringVar(&fromIP, "from", "", "IP address to find (required)")
	flag.StringVar(&toIP, "to", "", "IP address to replace with (required)")
	flag.Parse()

	if dbPath == "" || fromIP == "" || toIP == "" {
		log.Fatal("All flags are required: -db, -from, -to")
	}

	db, err := sql.Open("sqlite", dbPath)
	if err != nil {
		log.Fatal("Failed to open database:", err)
	}
	defer db.Close()

	tx, err := db.Begin()
	if err != nil {
		log.Fatal("Failed to begin transaction:", err)
	}
	defer tx.Rollback()

	rows, err := tx.Query(`SELECT id, url, description, icon_json FROM "main"."item_icon"`)
	if err != nil {
		log.Fatal("Failed to query table:", err)
	}
	defer rows.Close()

	updatedCount := 0

	for rows.Next() {
		var id int
		var url, description, iconJSONStr sql.NullString

		err := rows.Scan(&id, &url, &description, &iconJSONStr)
		if err != nil {
			log.Printf("Failed to scan row id=%d: %v", id, err)
			continue
		}

		updated := false
		newURL := url.String
		newDescription := description.String
		newIconJSONStr := iconJSONStr.String

		// 替换 url
		if url.Valid {
			replaced, ok := replaceIPExactly(url.String, fromIP, toIP)
			if ok {
				newURL = replaced
				updated = true
			}
		}

		// 替换 description
		if description.Valid {
			replaced, ok := replaceIPExactly(description.String, fromIP, toIP)
			if ok {
				newDescription = replaced
				updated = true
			}
		}

		// 替换 icon_json 中的 src（仅当 itemType == 2）
		if iconJSONStr.Valid {
			var icon IconJSON
			if err := json.Unmarshal([]byte(iconJSONStr.String), &icon); err == nil {
				if icon.ItemType == 2 && icon.Src != "" {
					replaced, ok := replaceIPExactly(icon.Src, fromIP, toIP)
					if ok {
						icon.Src = replaced
						newIconJSON, err := json.Marshal(icon)
						if err != nil {
							log.Printf("Failed to marshal icon_json for id=%d: %v", id, err)
						} else {
							newIconJSONStr = string(newIconJSON)
							updated = true
						}
					}
				}
			} else {
				log.Printf("Failed to unmarshal icon_json for id=%d: %v", id, err)
			}
		}

		if updated {
			_, err := tx.Exec(
				`UPDATE "main"."item_icon" SET url = ?, description = ?, icon_json = ? WHERE id = ?`,
				newURL, newDescription, newIconJSONStr, id,
			)
			if err != nil {
				log.Printf("Failed to update row id=%d: %v", id, err)
			} else {
				updatedCount++
			}
		}
	}

	if err := rows.Err(); err != nil {
		log.Fatal("Error iterating rows:", err)
	}

	if updatedCount > 0 {
		if err := tx.Commit(); err != nil {
			log.Fatal("Failed to commit transaction:", err)
		}
		fmt.Printf("Successfully updated %d rows.\n", updatedCount)
	} else {
		fmt.Println("No rows matched the IP to replace.")
	}
}
